import { ExternalLink } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; import { permanentRedirect } from 'next/navigation'; import { CompareButton } from '@/components/compare/compare-button'; import { CompareTrayBar } from '@/components/compare/compare-tray-bar'; import { RelationsBlock, SourcesTable, TimelineList } from '@/components/entity/blocks'; import { buildMetadata, loadEntity } from '@/components/entity/load'; import { QUANTS } from '@/components/hardware/fit-form'; import { fmtMemory, HW_KIND_LABELS, memoryOptions } from '@/components/hardware/hardware-bits'; import { EstimateBanner, FitBreakdownList, Methodology, SourceTag } from '@/components/intelligence/bits'; import { ViewBeacon } from '@/components/layout/view-beacon'; import { Chip, EntityBadge, Estimated, OpennessBadge, StatusBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { EntityLink, QualityMark } from '@/components/ui/entity'; import { KeyValue } from '@/components/ui/key-value'; import { Container, Note, Section } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { WatchButton } from '@/components/watchlist/watch-button'; import { api, intel, safe } from '@/lib/api'; import { fmtGb, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format'; import { PROSE_KEYS, routes, SITE_NAME, SITE_URL } from '@/lib/site'; export const revalidate = 600; type Params = { params: Promise<{ slug: string }>; searchParams: Promise> }; const CONTEXTS = [8192, 32768, 131072]; export async function generateMetadata({ params }: Params): Promise { const { slug } = await params; const d = await safe(api.entityOfType('hardware', slug)); if (!d || d.entity_type !== 'hardware') return { title: 'Hardware', robots: { index: false } }; const m = buildMetadata(d); const a = d.attributes ?? {}; return { ...m, title: `${d.name} — specs and which AI models fit (estimated)`, description: `${d.name}${typeof a.manufacturer === 'string' ? ` by ${a.manufacturer}` : ''}: ${fmtMemory(a.memory_gb)} memory${num(a.memory_bandwidth_gbs) !== null ? `, ${fmtInt(a.memory_bandwidth_gbs)} GB/s` : ''} as published, with sources — and the models estimated to fit at 4-bit, 8-bit and fp16. ${SITE_NAME}.` }; } export default async function HardwarePage({ params, searchParams }: Params) { const { slug } = await params; const sp = await searchParams; const d = await loadEntity('hardware', slug); const canonical = routes.entity(d); if (canonical !== `/hardware/${encodeURIComponent(slug)}`) permanentRedirect(canonical); const a = d.attributes ?? {}; const quant = QUANTS.some((q) => q.value === sp.quant) ? (sp.quant as string) : '4bit'; const ctx = num(sp.context); const context = ctx !== null && ctx > 0 ? Math.round(ctx) : 8192; const memChoice = num(sp.memory_gb); const options = memoryOptions(a.memory_gb); const fit = await safe(intel.hardwareSlugFit(d.slug, { quant, context, memory_gb: memChoice ?? undefined, limit: 120 })); const memoryUsed = num(fit?.inputs?.memory_gb) ?? memChoice ?? options.at(-1) ?? null; const self = (patch: { quant?: string; context?: number; memory_gb?: number | null }) => { const p = new URLSearchParams(); const q = patch.quant ?? quant; const c = patch.context ?? context; const m = patch.memory_gb === undefined ? memChoice : patch.memory_gb; if (q !== '4bit') p.set('quant', q); if (c !== 8192) p.set('context', String(c)); if (m !== null && m !== undefined) p.set('memory_gb', String(m)); const s = p.toString(); return `${canonical}${s ? `?${s}` : ''}#fit`; }; const specRows = Object.keys(a) .filter((k) => !PROSE_KEYS.has(k)) .map((k) => ({ key: k, raw: a[k] })); const items = fit?.items ?? []; const ld = { '@context': 'https://schema.org', '@type': 'Product', name: d.name, brand: typeof a.manufacturer === 'string' ? { '@type': 'Brand', name: a.manufacturer } : undefined, category: typeof a.kind === 'string' ? HW_KIND_LABELS[a.kind] ?? a.kind : 'Hardware', description: d.description ?? undefined, url: `${SITE_URL}${canonical}`, additionalProperty: [ options.length ? { '@type': 'PropertyValue', name: 'Memory', value: fmtMemory(a.memory_gb) } : null, num(a.memory_bandwidth_gbs) !== null ? { '@type': 'PropertyValue', name: 'Memory bandwidth', value: `${fmtInt(a.memory_bandwidth_gbs)} GB/s` } : null, ].filter(Boolean), }; const chip = (on: boolean) => `inline-flex h-9 items-center border px-2.5 text-xs font-medium ${on ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink'}`; return (